I'm Terrence

MobX Flutter 数据流动原理篇

背景

MoTouch 项目中的状态管理大部分是基于 MobX 的, 使用方法就不在这里说了,详见 MobX官网

使用过的同学们都知道,当 Observer builder 里面某个 MobX 属性发生改变时,就会自动刷新 Observer 了。但我一直有个疑问,为什么数据不用进行显式的绑定,到底是在哪里进行绑定的?如何才能确定某个属性已经被正确监听了?带着这些疑问,我们一起学习下源码。

类结构分工

Atom

被观察对象
Atom 对象是由 xxx.g.dart 生成, 实际上,我们每标记一个 @observable 属性,在 .g.dart 生成 getter 跟 setter 及对应的 atom, 通过 _atom.reportRead()_atom.reportWrite() 来触发 ReactiveContext 的数据绑定及分发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = _Counter with _$Counter; //外面实例化的是这个 Counter, 实际上 是把_$Counter 这个 Mixin 实例化了

abstract class _Counter with Store {
@observable
int value = 0;

@observable
ObservableMap<String, Size> uidVideoSizeMap = ObservableMap();

@action
void increment() {
value++;
}
}
1
2
3
4
5
6
7
8
9

part of 'counter.dart';

mixin _$Counter on _Counter, Store {// _$Counter 又继承了_Counter
final _$valueAtom = Atom(name: '_Counter.value');
···
final _$uidVideoSizeMapAtom = Atom(name: '_Counter.uidVideoSizeMap');
···
}

Observer

0.3.8

项目最早是用 0.3.8 版本的,很有印象,Observer 其核心就是个 StatefulWidget 而已,一旦数据变化,内部通过调用 setState(), 触发 State 的刷新,从而触发 builder 的刷新。

1
void invalidate() => setState(noOp);

1.1.0

而到了 1.1.0 版本,Observer 的实现就有所不同了,不再是个StatefulWidget, 而是个 StatelessWidget了。主角是 elemtent , 核心通过

1
void invalidate() => markNeedsBuild();

标记 当前 element 为 dirty, 在下一帧触发相应的 build 方法。

Reaction(ReactionImpl,Derivation)

被 Observer 持有,封装数据绑定,更新回调方法。Reaction.run 回调给 Observer

ReactiveContext

1
final ReactiveContext mainContext = createContext(config: ReactiveConfig.main);

是个巨大的单例,负责处理 Atom 跟 Reaction 的依赖关系, 及进行数据方法绑定、分发、解绑等逻辑。

数据流动过程

数据绑定

整个数据绑定过程,在 0.3.8 版本是发生在 Observer State 的 build 里面,而在 1.1.0 版本,是在 Observer Element 的 build 方法体内。

1. start tracking

在 ReactiveContext 单例记录当前的 derivation。

1
2
3
4
5
6
7
8
9
Derivation _startTracking(Derivation derivation) {
final prevDerivation = _state.trackingDerivation;
_state.trackingDerivation = derivation;

_resetDerivationState(derivation);
derivation._newObservables = {};

return prevDerivation;
}

2. reportObserved()

image-20200623114315072

看堆栈可以知道,对于每一次 @Observable 对象的 get 调用,实际上是 atom.reportObserved() ,最终调用ReaciveContext 的 _reportObserved

1
2
3
4
5
6
7
8
9
10
11
12
13
void _reportObserved(Atom atom) {
final derivation = _state.trackingDerivation;

if (derivation != null) {
// 把 atom 加到当前的 derivation 的新观察队列里面
derivation._newObservables.add(atom);
if (!atom._isBeingObserved) {
atom
.._isBeingObserved = true
.._notifyOnBecomeObserved();
}
}
}

3. endTracking

image-20200622143205460

把在 startTracking 跟 endTracking 之间, 所有被调用 reportRead() 的 atom, 绑定当前观察者 derivation 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void _bindDependencies(Derivation derivation) {
// 这里对 Set 搞了两次difference, 目的是把新、旧 atoms 分开。旧的清空数据,新的绑定观察者
final staleObservables =
derivation._observables.difference(derivation._newObservables);
final newObservables =
derivation._newObservables.difference(derivation._observables);
var lowestNewDerivationState = DerivationState.upToDate;

// Add newly found observables
for (final observable in newObservables) {
observable._addObserver(derivation);

// Computed = Observable + Derivation
if (observable is Computed) {
if (observable._dependenciesState.index >
lowestNewDerivationState.index) {
lowestNewDerivationState = observable._dependenciesState;
}
}
}

// Remove previous observables
for (final ob in staleObservables) {
ob._removeObserver(derivation);
}

if (lowestNewDerivationState != DerivationState.upToDate) {
derivation
.._dependenciesState = lowestNewDerivationState
.._onBecomeStale();
}

derivation
.._observables = derivation._newObservables
.._newObservables = {}; // No need for newObservables beyond this point
}

数据更新

reportWrite()

当数据更新 atom.reportWrite() 主要做了这两件事:

  1. 更新 数据
  2. 把 与之绑定 derivation (即 reaction) 加到队列。
1
2
3
4
5
6
7
8
9
10
11
12
13
void reportWrite<T>(T newValue, T oldValue, void Function() setNewValue) {
context.spyReport(ObservableValueSpyEvent(this,
newValue: newValue, oldValue: oldValue, name: name));

// ignore: cascade_invocations
context.conditionallyRunInAction(() {
setNewValue();
reportChanged();//触发 Context.addPendingReaction(reaction)
}, this, name: '${name}_set');

// ignore: cascade_invocations
context.spyReport(EndedSpyEvent(type: 'observable', name: name));
}
1
2
3
4
// 把 reaction 添加到队列, 这里 reaction 就是 ReactionImpl
void addPendingReaction(Reaction reaction) {
_state.pendingReactions.add(reaction);
}

数据分发

@action

image-20200622151359216

不带 @action

image-20200622152046571

带不带 @action 的区别,其实就是 下面这个地方有没把 ActionController 传入来,数据流向其实是一样的,都会由 controller.endAction(runInfo); 来触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void conditionallyRunInAction(void Function() fn, Atom atom,
{String name, ActionController actionController}) {
if (isWithinBatch) {
enforceWritePolicy(atom);
fn();
} else {
final controller = actionController ??
ActionController(
context: this, name: name ?? nameFor('conditionallyRunInAction'));
final runInfo = controller.startAction();

try {
enforceWritePolicy(atom);
fn();
} finally {
controller.endAction(runInfo);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void _runReactionsInternal() {
_state.isRunningReactions = true;

//从队列读取 reaction
var iterations = 0;
final allReactions = _state.pendingReactions;


while (allReactions.isNotEmpty) {
// 这里是抛出死循环的情况
if (++iterations == config.maxIterations) {
final failingReaction = allReactions[0];

// Resetting ensures we have no bad-state left
_resetState();

throw MobXCyclicReactionException(
"Reaction doesn't converge to a stable state after ${config.maxIterations} iterations. Probably there is a cycle in the reactive function: $failingReaction");
}

final remainingReactions = allReactions.toList(growable: false);
allReactions.clear();
for (final reaction in remainingReactions) {
reaction._run();//分发,回调给 Observer 层
}
}

_state
..pendingReactions = []
..isRunningReactions = false;
}

最终触发 rebuild

0.3.8

1
2
3
4
//observer.dart
class ObserverState extends State<Observer> {
void invalidate() => setState(noOp);
}

1.1.0

1
2
3
4
5
6

//observer_widget_mixin.dart
mixin ObserverElementMixin on ComponentElement {
//reaction.run 回调给 Observer 层,通过 markNeedsBuild 触发 rebuild
void invalidate() => markNeedsBuild();
}

析构

因为 MobX 里面存在一个 ReactiveContext 单例,那就涉及到对数据的清除绑定了

image-20200622164901647

1
2
3
4
5
6
7
8
9
10
11
void _clearObservables(Derivation derivation) {
final observables = derivation._observables;
derivation._observables = {};

for (final x in observables) {
//打破 atom 跟 reaction 的双向依赖
x._removeObserver(derivation);
}

derivation._dependenciesState = DerivationState.notTracking;
}

应用

在实际开发过程中,我们项目会遇到一些数据更新了,但没触发 Observer rebuild 的一些疑问,在弄清楚数据流向后,现在可以基本解决了。

Counter 还是用上文那个例子,我们这次是对其中MobX 提供的 ObservableMap 进行监听,意图监听 map 的增减操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:motouch/UI/Me/counter.dart';

class CounterExample extends StatefulWidget {
const CounterExample();

@override
CounterExampleState createState() => CounterExampleState();
}

class CounterExampleState extends State<CounterExample> {
final Counter counter = Counter();

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: const Text('MobX Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Observer(
builder: (_) {
//bind 1
counter.uidVideoSizeMap;

//bind 2
// counter.uidVideoSizeMap.length;

print('rebuild');
return Container();
},
),
MaterialButton(
child: Text('对 counter.uidVideoSizeMap 重新赋值'),
onPressed: () {
//update 1
print('对 counter.uidVideoSizeMap 重新赋值');
counter.uidVideoSizeMap = ObservableMap();
},
),
MaterialButton(
child: Text('counter.uidVideoSizeMap 增减元素'),
onPressed: () {
//update 2
print('counter.uidVideoSizeMap 增减元素');
counter.uidVideoSizeMap['1'] = Size.zero;
},
),
],
),
),
);
}

分绑定分2种方式,我们先注释 bind 2,用 counter.uidVideoSizeMap绑定,看看打印结果:

1
2
3
4
5
6
7
8
I/flutter (20534): 对 counter.uidVideoSizeMap 重新赋值
I/flutter (20534): rebuild
I/flutter (20534): 对 counter.uidVideoSizeMap 重新赋值
I/flutter (20534): rebuild
I/flutter (20534): 对 counter.uidVideoSizeMap 重新赋值
I/flutter (20534): rebuild
I/flutter (20534): counter.uidVideoSizeMap 增减元素
I/flutter (20534): counter.uidVideoSizeMap 增减元素

可见,bind 1 这种方式,增减元素是不会引起 Observer 的 rebuild 的。

再来看看注释 bind 1, 打开 counter.uidVideoSizeMap.length的结果

1
2
3
4
5
6
7
I/flutter (20534): 对 counter.uidVideoSizeMap 重新赋值
I/flutter (20534): rebuild
I/flutter (20534): 对 counter.uidVideoSizeMap 重新赋值
I/flutter (20534): rebuild
I/flutter (20534): counter.uidVideoSizeMap 增减元素
I/flutter (20534): rebuild
I/flutter (20534): counter.uidVideoSizeMap 增减元素

bind 2 的方式,无论是重新赋值,还是增减元素,都能引起 Observer 的 rebuild 。

结合源码来分析,bind 1这种绑定方式:

1
2
3
4
5
6
7
8
9
//counter.g.dart
// 在 Observer builder 方法体内,每次 counter.uidVideoSizeMap, 触发的是 _Counter.uidVideoSizeMap 这个属性 atom 的 reportRead(), 绑定的是这个属性本身,跟 ObserverMap 的类型无关。
final _$uidVideoSizeMapAtom = Atom(name: '_Counter.uidVideoSizeMap');

@override
ObservableMap<String, Size> get uidVideoSizeMap {
_$uidVideoSizeMapAtom.reportRead();//内里调用 reportObserver()
return super.uidVideoSizeMap;
}

bind 2:

1
2
3
4
5
6
7
8
9
//observable_map.dart 
// 在 Observer builder 方法体内,调用 counter.uidVideoSizeMap.length,是把 Observer_map 里面实现 的 _atom 给绑定了。
@override
int get length {
_context.enforceReadPolicy(_atom);

_atom.reportObserved();//绑定
return _map.length;
}

可见, bind 1, bind 2两种绑定方式,决定了 reaction 的不同, bind 1 那种方式完全把 uidVideoSizeMap 当成普通类型来用了,压根没有把 ObservableMap 类型带给我们便利给用上。

ObservableList, ObservableSet 也同理。